
namespace createjs {
	export class Sprite extends DisplayObject {
		currentFrame: number;
		currentAnimation: any;
		paused: boolean;
		spriteSheet: SpriteSheet;
		currentAnimationFrame: number;
		framerate: number;
		_animation: any;
		_currentFrame: any;
		_skipAdvance: boolean;

		constructor(spriteSheet?, frameOrAnimation?) {
			super();

			this.currentFrame = 0;

			this.currentAnimation = null;

			this.paused = true;

			this.spriteSheet = spriteSheet;

			this.currentAnimationFrame = 0;

			this.framerate = 0;

			this._animation = null;

			this._currentFrame = null;

			this._skipAdvance = false;

			this._webGLRenderStyle = createjs.DisplayObject._StageGL_SPRITE;

			if (frameOrAnimation != null) { this.gotoAndPlay(frameOrAnimation); }
		}

		isVisible() {
			var hasContent = this.spriteSheet.complete;
			return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent);
		};

		play() {
			this.paused = false;
		};

		stop() {
			this.paused = true;
		};

		gotoAndPlay(frameOrAnimation) {
			this.paused = false;
			this._skipAdvance = true;
			this._goto(frameOrAnimation);
		};

		gotoAndStop(frameOrAnimation) {
			this.paused = true;
			this._goto(frameOrAnimation);
		};

		advance(time) {
			var fps = this.framerate || this.spriteSheet.framerate;
			var t = (fps && time != null) ? time / (1000 / fps) : 1;
			this._normalizeFrame(t);
		};

		getBounds() {
			// TODO: should this normalizeFrame?
			return super.getBounds() || this.spriteSheet.getFrameBounds(this.currentFrame, this._rectangle);
		};

		clone() {
			return this._cloneProps(new Sprite(this.spriteSheet));
		};

		toString() {
			return "[Sprite (name=" + this.name + ")]";
		};

		_cloneProps(o: Sprite) {
			super._cloneProps(o);
			o.currentFrame = this.currentFrame;
			o.currentAnimation = this.currentAnimation;
			o.paused = this.paused;
			o.currentAnimationFrame = this.currentAnimationFrame;
			o.framerate = this.framerate;

			o._animation = this._animation;
			o._currentFrame = this._currentFrame;
			o._skipAdvance = this._skipAdvance;
			return o;
		};

		_tick(evtObj) {
			if (!this.paused) {
				if (!this._skipAdvance) {
					this.advance(evtObj && evtObj.delta);
				}
				this._skipAdvance = false;
			}
			super._tick(evtObj);
		};

		_normalizeFrame(frameDelta?) {
			frameDelta = frameDelta || 0;
			var animation = this._animation;
			var paused = this.paused;
			var frame = this._currentFrame;
			var l;

			if (animation) {
				var speed = animation.speed || 1;
				var animFrame = this.currentAnimationFrame;
				l = animation.frames.length;
				if (animFrame + frameDelta * speed >= l) {
					var next = animation.next;
					if (this._dispatchAnimationEnd(animation, frame, paused, next, l - 1)) {
						// something changed in the event stack, so we shouldn't make any more changes here.
						return;
					} else if (next) {
						// sequence. Automatically calls _normalizeFrame again with the remaining frames.
						return this._goto(next, frameDelta - (l - animFrame) / speed);
					} else {
						// end.
						this.paused = true;
						animFrame = animation.frames.length - 1;
					}
				} else {
					animFrame += frameDelta * speed;
				}
				this.currentAnimationFrame = animFrame;
				this._currentFrame = animation.frames[animFrame | 0]
			} else {
				frame = (this._currentFrame += frameDelta);
				l = this.spriteSheet.getNumFrames();
				if (frame >= l && l > 0) {
					if (!this._dispatchAnimationEnd(animation, frame, paused, l - 1)) {
						// looped.
						if ((this._currentFrame -= l) >= l) { return this._normalizeFrame(); }
					}
				}
			}
			frame = this._currentFrame | 0;
			if (this.currentFrame != frame) {
				this.currentFrame = frame;
				this.dispatchEvent("change");
			}
		};

		_dispatchAnimationEnd(animation, frame, paused, next, end?) {
			var name = animation ? animation.name : null;
			if (this.hasEventListener("animationend")) {
				var evt = new createjs.Event("animationend");
				evt.name = name;
				evt.next = next;
				this.dispatchEvent(evt);
			}
			// did the animation get changed in the event stack?:
			var changed = (this._animation != animation || this._currentFrame != frame);
			// if the animation hasn't changed, but the sprite was paused, then we want to stick to the last frame:
			if (!changed && !paused && this.paused) { this.currentAnimationFrame = end; changed = true; }
			return changed;
		};

		_goto(frameOrAnimation, frame?) {
			this.currentAnimationFrame = 0;
			if (isNaN(frameOrAnimation)) {
				var data = this.spriteSheet.getAnimation(frameOrAnimation);
				if (data) {
					this._animation = data;
					this.currentAnimation = frameOrAnimation;
					this._normalizeFrame(frame);
				}
			} else {
				this.currentAnimation = this._animation = null;
				this._currentFrame = frameOrAnimation;
				this._normalizeFrame();
			}
		};
	}

}
